package fit.decorator.util;

import java.text.ParseException;

import fit.Parse;
import fit.decorator.exceptions.InvalidInputException;

public class Table {
	private final Parse table;

	public Table(Parse table) {
		this.table = table;
	}

	public Table(String html) throws ParseException {
		this.table = new Parse(html);
	}

	public Parse incrementColumnValues(int numberOfTimes, String columnName,
			Delta delta) throws InvalidInputException {
		int headerRowIndex = rowNumberContainingText(columnName);
		copyAndAppendLastRow(numberOfTimes - 1);
		incrementColumnValues(columnName, delta, headerRowIndex);
		return table;
	}

	public void insertAsFirstRow(Parse firstRow) {
		firstRow.more = table.parts;
		table.parts = firstRow;
	}

	public Parse stripFirstRow() {
		Parse firstRow = table.parts;
		table.parts = table.parts.more;
		return firstRow;
	}

	@Override
	public String toString() {
		return toSimpleText(table, new StringBuffer());
	}

	int columnNumberContainingText(String columnName, int headerRowIndex)
			throws InvalidInputException {
		int columnNumber = -1;
		Parse columns = table.at(0, headerRowIndex, 0);
		while (columns != null) {
			columnNumber++;
			if (columnName.equals(columns.text())) {
				return columnNumber;
			}
			columns = columns.more;
		}
		throw new InvalidInputException(errorMsg(columnName));
	}

	String columnValue(int rowIndex, int columnIndex) {
		return table.at(0, rowIndex, columnIndex).text();
	}

	public void copyAndAppendLastRow(int numberOfTimes) {
		if (numberOfTimes > 0 && tableHasMoreThanTwoRows()) {
			Parse lastRow = lastRow();
			Parse secondLastRow = secondLastRow(lastRow);
			copyAndAppend(lastRow, numberOfTimes);
			secondLastRow.more = lastRow;
		}
	}

	void incrementColumnValues(String columnName, Delta delta,
			int headerRowIndex) throws InvalidInputException {
		int columnNumber = columnNumberContainingText(columnName,
				headerRowIndex);
		int totalNumberOfRows = numberOfRows();
		for (int i = headerRowIndex + 2; i < totalNumberOfRows; ++i) {
			Parse columnToBeUpdated = table.at(0, i, columnNumber);
			String value = columnToBeUpdated.text();
			value = delta.addTo(value, i - headerRowIndex - 1);
			columnToBeUpdated.body = value;
		}
	}

	Parse lastRow() {
		return table.parts.last();
	}

	int numberOfRows() {
		return table.parts.size();
	}

	int rowNumberContainingText(String searchText) throws InvalidInputException {
		Parse rows = table.at(0, 0);
		int numberOfRows = rows.size();
		for (int i = 0; i < numberOfRows; i++) {
			Parse columns = table.at(0, i, 0);
			int numberOfColumns = columns.size();
			for (int j = 0; j < numberOfColumns; ++j) {
				if (searchText.equals(table.at(0, i, j).text())) {
					return i;
				}
			}
		}
		throw new InvalidInputException(errorMsg(searchText));
	}

	@SuppressWarnings("null")
	Parse secondLastRow(Parse lastRow) {
		Parse nextRow = table.parts;
		Parse currentRow = null;
		while (nextRow != lastRow) {
			currentRow = nextRow;
			nextRow = nextRow.more;
		}
		currentRow.more = null;
		return currentRow;
	}

	private void copyAndAppend(Parse lastRow, int numberOfTimes) {
		for (int i = 0; i < numberOfTimes; i++) {
			Parse columns = lastRow.parts;
			Parse nextColumn = columns.more;
			Parse newNextColumn = newParse(nextColumn, nextColumn.more);
			Parse newColumn = newParse(columns, newNextColumn);
			Parse newRow = new Parse(stripAngularBrackets(lastRow.tag),
					lastRow.body, newColumn, null);
			lastRow.last().more = newRow;
		}
	}

	private Parse newParse(Parse columns, Parse nextColumn) {
		return new Parse(stripAngularBrackets(columns.tag), columns.body,
				columns.parts, nextColumn);
	}

	private String errorMsg(String searchText) {
		return "'" + searchText + "' was not found in the table " + toString();
	}

	private void simpleTextOfLeave(Parse table2, StringBuffer returnText) {
		returnText.append(table2.tag).append(table2.text()).append(table2.end);
	}

	private void simpleTextOfMore(Parse table2, StringBuffer returnText) {
		if ((table2.more != null)) {
			toSimpleText(table2.more, returnText);
		}
	}

	private void simpleTextOfParts(Parse table2, StringBuffer returnText) {
		returnText.append(table2.tag);
		toSimpleText(table2.parts, returnText);
		returnText.append(table2.end);
	}

	private String stripAngularBrackets(String tag) {
		return tag.substring(1, tag.length() - 1);
	}

	private String toSimpleText(Parse table2, StringBuffer returnText) {
		if (table2.parts == null) {
			simpleTextOfLeave(table2, returnText);
			simpleTextOfMore(table2, returnText);
			return returnText.toString();
		}
		simpleTextOfParts(table2, returnText);
		simpleTextOfMore(table2, returnText);
		return returnText.toString();
	}

	public Parse table() {
		return table;
	}

	public Parse incrementColumnValuesByDelta(String columnName, Delta delta)
			throws InvalidInputException {
		int headerRowIndex = rowNumberContainingText(columnName);
		incrementColumnValues(columnName, delta, headerRowIndex);
		return table;
	}

	private boolean tableHasMoreThanTwoRows() {
		return (table.parts.size() > 2);
	}
}
